/*-------------------------------------------------------------------------
 *
 * wparser.c
 *		词解析器的标准接口
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/tsearch/wparser.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "common/jsonapi.h"
#include "funcapi.h"
#include "tsearch/ts_cache.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"
#include "utils/jsonfuncs.h"
#include "utils/varlena.h"

/******sql-level interface******/

typedef struct
{
	int			cur;
	LexDescr   *list;
} TSTokenTypeStorage;

/* ts_headline_json_* 的状态 */
typedef struct HeadlineJsonState
{
	HeadlineParsedText *prs;
	TSConfigCacheEntry *cfg;
	TSParserCacheEntry *prsobj;
	TSQuery		query;
	List	   *prsoptions;
	bool		transformed;
} HeadlineJsonState;

static text *fc_headline_json_value(void *fc__state, char *fc_elem_value, int fc_elem_len);

static void fc_tt_setup_firstcall(FuncCallContext *fc_funcctx, Oid fc_prsid)
{
	TupleDesc	fc_tupdesc;
	MemoryContext fc_oldcontext;
	TSTokenTypeStorage *fc_st;
	TSParserCacheEntry *fc_prs = lookup_ts_parser_cache(fc_prsid);

	if (!OidIsValid(fc_prs->lextypeOid))
		elog(ERROR, "method lextype isn't defined for text search parser %u",
			 fc_prsid);

	fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

	fc_st = (TSTokenTypeStorage *) palloc(sizeof(TSTokenTypeStorage));
	fc_st->cur = 0;
	/* lextype 接受一个虚拟参数 */
	fc_st->list = (LexDescr *) DatumGetPointer(OidFunctionCall1(fc_prs->lextypeOid,
															 (Datum) 0));
	fc_funcctx->user_fctx = (void *) fc_st;

	fc_tupdesc = CreateTemplateTupleDesc(3);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "tokid",
					   INT4OID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "alias",
					   TEXTOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "description",
					   TEXTOID, -1, 0);

	fc_funcctx->attinmeta = TupleDescGetAttInMetadata(fc_tupdesc);
	MemoryContextSwitchTo(fc_oldcontext);
}

static Datum fc_tt_process_call(FuncCallContext *fc_funcctx)
{
	TSTokenTypeStorage *fc_st;

	fc_st = (TSTokenTypeStorage *) fc_funcctx->user_fctx;
	if (fc_st->list && fc_st->list[fc_st->cur].lexid)
	{
		Datum		fc_result;
		char	   *fc_values[3];
		char		fc_txtid[16];
		HeapTuple	fc_tuple;

		sprintf(fc_txtid, "%d", fc_st->list[fc_st->cur].lexid);
		fc_values[0] = fc_txtid;
		fc_values[1] = fc_st->list[fc_st->cur].alias;
		fc_values[2] = fc_st->list[fc_st->cur].descr;

		fc_tuple = BuildTupleFromCStrings(fc_funcctx->attinmeta, fc_values);
		fc_result = HeapTupleGetDatum(fc_tuple);

		pfree(fc_values[1]);
		pfree(fc_values[2]);
		fc_st->cur++;
		return fc_result;
	}
	return (Datum) 0;
}

Datum ts_token_type_byid(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	Datum		fc_result;

	if (SRF_IS_FIRSTCALL())
	{
		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_tt_setup_firstcall(fc_funcctx, PG_GETARG_OID(0));
	}

	fc_funcctx = SRF_PERCALL_SETUP();

	if ((fc_result = fc_tt_process_call(fc_funcctx)) != (Datum) 0)
		SRF_RETURN_NEXT(fc_funcctx, fc_result);
	SRF_RETURN_DONE(fc_funcctx);
}

Datum ts_token_type_byname(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	Datum		fc_result;

	if (SRF_IS_FIRSTCALL())
	{
		text	   *fc_prsname = PG_GETARG_TEXT_PP(0);
		Oid			fc_prsId;

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_prsId = get_ts_parser_oid(textToQualifiedNameList(fc_prsname), false);
		fc_tt_setup_firstcall(fc_funcctx, fc_prsId);
	}

	fc_funcctx = SRF_PERCALL_SETUP();

	if ((fc_result = fc_tt_process_call(fc_funcctx)) != (Datum) 0)
		SRF_RETURN_NEXT(fc_funcctx, fc_result);
	SRF_RETURN_DONE(fc_funcctx);
}

typedef struct
{
	int			type;
	char	   *lexeme;
} LexemeEntry;

typedef struct
{
	int			cur;
	int			len;
	LexemeEntry *list;
} PrsStorage;


static void fc_prs_setup_firstcall(FuncCallContext *fc_funcctx, Oid fc_prsid, text *fc_txt)
{
	TupleDesc	fc_tupdesc;
	MemoryContext fc_oldcontext;
	PrsStorage *fc_st;
	TSParserCacheEntry *fc_prs = lookup_ts_parser_cache(fc_prsid);
	char	   *fc_lex = NULL;
	int			fc_llen = 0,
				fc_type = 0;
	void	   *fc_prsdata;

	fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

	fc_st = (PrsStorage *) palloc(sizeof(PrsStorage));
	fc_st->cur = 0;
	fc_st->len = 16;
	fc_st->list = (LexemeEntry *) palloc(sizeof(LexemeEntry) * fc_st->len);

	fc_prsdata = (void *) DatumGetPointer(FunctionCall2(&fc_prs->prsstart,
													 PointerGetDatum(VARDATA_ANY(fc_txt)),
													 Int32GetDatum(VARSIZE_ANY_EXHDR(fc_txt))));

	while ((fc_type = DatumGetInt32(FunctionCall3(&fc_prs->prstoken,
											   PointerGetDatum(fc_prsdata),
											   PointerGetDatum(&fc_lex),
											   PointerGetDatum(&fc_llen)))) != 0)
	{
		if (fc_st->cur >= fc_st->len)
		{
			fc_st->len = 2 * fc_st->len;
			fc_st->list = (LexemeEntry *) repalloc(fc_st->list, sizeof(LexemeEntry) * fc_st->len);
		}
		fc_st->list[fc_st->cur].lexeme = palloc(fc_llen + 1);
		memcpy(fc_st->list[fc_st->cur].lexeme, fc_lex, fc_llen);
		fc_st->list[fc_st->cur].lexeme[fc_llen] = '\0';
		fc_st->list[fc_st->cur].type = fc_type;
		fc_st->cur++;
	}

	FunctionCall1(&fc_prs->prsend, PointerGetDatum(fc_prsdata));

	fc_st->len = fc_st->cur;
	fc_st->cur = 0;

	fc_funcctx->user_fctx = (void *) fc_st;
	fc_tupdesc = CreateTemplateTupleDesc(2);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "tokid",
					   INT4OID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "token",
					   TEXTOID, -1, 0);

	fc_funcctx->attinmeta = TupleDescGetAttInMetadata(fc_tupdesc);
	MemoryContextSwitchTo(fc_oldcontext);
}

static Datum fc_prs_process_call(FuncCallContext *fc_funcctx)
{
	PrsStorage *fc_st;

	fc_st = (PrsStorage *) fc_funcctx->user_fctx;
	if (fc_st->cur < fc_st->len)
	{
		Datum		fc_result;
		char	   *fc_values[2];
		char		fc_tid[16];
		HeapTuple	fc_tuple;

		fc_values[0] = fc_tid;
		sprintf(fc_tid, "%d", fc_st->list[fc_st->cur].type);
		fc_values[1] = fc_st->list[fc_st->cur].lexeme;
		fc_tuple = BuildTupleFromCStrings(fc_funcctx->attinmeta, fc_values);
		fc_result = HeapTupleGetDatum(fc_tuple);

		pfree(fc_values[1]);
		fc_st->cur++;
		return fc_result;
	}
	return (Datum) 0;
}

Datum ts_parse_byid(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	Datum		fc_result;

	if (SRF_IS_FIRSTCALL())
	{
		text	   *fc_txt = PG_GETARG_TEXT_PP(1);

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_prs_setup_firstcall(fc_funcctx, PG_GETARG_OID(0), fc_txt);
		PG_FREE_IF_COPY(fc_txt, 1);
	}

	fc_funcctx = SRF_PERCALL_SETUP();

	if ((fc_result = fc_prs_process_call(fc_funcctx)) != (Datum) 0)
		SRF_RETURN_NEXT(fc_funcctx, fc_result);
	SRF_RETURN_DONE(fc_funcctx);
}

Datum ts_parse_byname(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	Datum		fc_result;

	if (SRF_IS_FIRSTCALL())
	{
		text	   *fc_prsname = PG_GETARG_TEXT_PP(0);
		text	   *fc_txt = PG_GETARG_TEXT_PP(1);
		Oid			fc_prsId;

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_prsId = get_ts_parser_oid(textToQualifiedNameList(fc_prsname), false);
		fc_prs_setup_firstcall(fc_funcctx, fc_prsId, fc_txt);
	}

	fc_funcctx = SRF_PERCALL_SETUP();

	if ((fc_result = fc_prs_process_call(fc_funcctx)) != (Datum) 0)
		SRF_RETURN_NEXT(fc_funcctx, fc_result);
	SRF_RETURN_DONE(fc_funcctx);
}

Datum ts_headline_byid_opt(PG_FUNCTION_ARGS)
{
	Oid			fc_tsconfig = PG_GETARG_OID(0);
	text	   *fc_in = PG_GETARG_TEXT_PP(1);
	TSQuery		fc_query = PG_GETARG_TSQUERY(2);
	text	   *fc_opt = (PG_NARGS() > 3 && PG_GETARG_POINTER(3)) ? PG_GETARG_TEXT_PP(3) : NULL;
	HeadlineParsedText fc_prs;
	List	   *fc_prsoptions;
	text	   *fc_out;
	TSConfigCacheEntry *fc_cfg;
	TSParserCacheEntry *fc_prsobj;

	fc_cfg = lookup_ts_config_cache(fc_tsconfig);
	fc_prsobj = lookup_ts_parser_cache(fc_cfg->prsId);

	if (!OidIsValid(fc_prsobj->headlineOid))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("text search parser does not support headline creation")));

	memset(&fc_prs, 0, sizeof(HeadlineParsedText));
	fc_prs.lenwords = 32;
	fc_prs.words = (HeadlineWordEntry *) palloc(sizeof(HeadlineWordEntry) * fc_prs.lenwords);

	hlparsetext(fc_cfg->cfgId, &fc_prs, fc_query,
				VARDATA_ANY(fc_in), VARSIZE_ANY_EXHDR(fc_in));

	if (fc_opt)
		fc_prsoptions = deserialize_deflist(PointerGetDatum(fc_opt));
	else
		fc_prsoptions = NIL;

	FunctionCall3(&(fc_prsobj->prsheadline),
				  PointerGetDatum(&fc_prs),
				  PointerGetDatum(fc_prsoptions),
				  PointerGetDatum(fc_query));

	fc_out = generateHeadline(&fc_prs);

	PG_FREE_IF_COPY(fc_in, 1);
	PG_FREE_IF_COPY(fc_query, 2);
	if (fc_opt)
		PG_FREE_IF_COPY(fc_opt, 3);
	pfree(fc_prs.words);
	pfree(fc_prs.startsel);
	pfree(fc_prs.stopsel);

	PG_RETURN_POINTER(fc_out);
}

Datum ts_headline_byid(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_byid_opt,
										PG_GETARG_DATUM(0),
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(2)));
}

Datum ts_headline(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_byid_opt,
										ObjectIdGetDatum(getTSCurrentConfig(true)),
										PG_GETARG_DATUM(0),
										PG_GETARG_DATUM(1)));
}

Datum ts_headline_opt(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall4(ts_headline_byid_opt,
										ObjectIdGetDatum(getTSCurrentConfig(true)),
										PG_GETARG_DATUM(0),
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(2)));
}

Datum ts_headline_jsonb_byid_opt(PG_FUNCTION_ARGS)
{
	Oid			fc_tsconfig = PG_GETARG_OID(0);
	Jsonb	   *fc_jb = PG_GETARG_JSONB_P(1);
	TSQuery		fc_query = PG_GETARG_TSQUERY(2);
	text	   *fc_opt = (PG_NARGS() > 3 && PG_GETARG_POINTER(3)) ? PG_GETARG_TEXT_P(3) : NULL;
	Jsonb	   *fc_out;
	JsonTransformStringValuesAction fc_action = (JsonTransformStringValuesAction) fc_headline_json_value;
	HeadlineParsedText fc_prs;
	HeadlineJsonState *fc_state = palloc0(sizeof(HeadlineJsonState));

	memset(&fc_prs, 0, sizeof(HeadlineParsedText));
	fc_prs.lenwords = 32;
	fc_prs.words = (HeadlineWordEntry *) palloc(sizeof(HeadlineWordEntry) * fc_prs.lenwords);

	fc_state->prs = &fc_prs;
	fc_state->cfg = lookup_ts_config_cache(fc_tsconfig);
	fc_state->prsobj = lookup_ts_parser_cache(fc_state->cfg->prsId);
	fc_state->query = fc_query;
	if (fc_opt)
		fc_state->prsoptions = deserialize_deflist(PointerGetDatum(fc_opt));
	else
		fc_state->prsoptions = NIL;

	if (!OidIsValid(fc_state->prsobj->headlineOid))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("text search parser does not support headline creation")));

	fc_out = transform_jsonb_string_values(fc_jb, fc_state, fc_action);

	PG_FREE_IF_COPY(fc_jb, 1);
	PG_FREE_IF_COPY(fc_query, 2);
	if (fc_opt)
		PG_FREE_IF_COPY(fc_opt, 3);

	pfree(fc_prs.words);

	if (fc_state->transformed)
	{
		pfree(fc_prs.startsel);
		pfree(fc_prs.stopsel);
	}

	PG_RETURN_JSONB_P(fc_out);
}

Datum ts_headline_jsonb(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_jsonb_byid_opt,
										ObjectIdGetDatum(getTSCurrentConfig(true)),
										PG_GETARG_DATUM(0),
										PG_GETARG_DATUM(1)));
}

Datum ts_headline_jsonb_byid(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_jsonb_byid_opt,
										PG_GETARG_DATUM(0),
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(2)));
}

Datum ts_headline_jsonb_opt(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall4(ts_headline_jsonb_byid_opt,
										ObjectIdGetDatum(getTSCurrentConfig(true)),
										PG_GETARG_DATUM(0),
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(2)));
}

Datum ts_headline_json_byid_opt(PG_FUNCTION_ARGS)
{
	Oid			fc_tsconfig = PG_GETARG_OID(0);
	text	   *fc_json = PG_GETARG_TEXT_P(1);
	TSQuery		fc_query = PG_GETARG_TSQUERY(2);
	text	   *fc_opt = (PG_NARGS() > 3 && PG_GETARG_POINTER(3)) ? PG_GETARG_TEXT_P(3) : NULL;
	text	   *fc_out;
	JsonTransformStringValuesAction fc_action = (JsonTransformStringValuesAction) fc_headline_json_value;

	HeadlineParsedText fc_prs;
	HeadlineJsonState *fc_state = palloc0(sizeof(HeadlineJsonState));

	memset(&fc_prs, 0, sizeof(HeadlineParsedText));
	fc_prs.lenwords = 32;
	fc_prs.words = (HeadlineWordEntry *) palloc(sizeof(HeadlineWordEntry) * fc_prs.lenwords);

	fc_state->prs = &fc_prs;
	fc_state->cfg = lookup_ts_config_cache(fc_tsconfig);
	fc_state->prsobj = lookup_ts_parser_cache(fc_state->cfg->prsId);
	fc_state->query = fc_query;
	if (fc_opt)
		fc_state->prsoptions = deserialize_deflist(PointerGetDatum(fc_opt));
	else
		fc_state->prsoptions = NIL;

	if (!OidIsValid(fc_state->prsobj->headlineOid))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("text search parser does not support headline creation")));

	fc_out = transform_json_string_values(fc_json, fc_state, fc_action);

	PG_FREE_IF_COPY(fc_json, 1);
	PG_FREE_IF_COPY(fc_query, 2);
	if (fc_opt)
		PG_FREE_IF_COPY(fc_opt, 3);
	pfree(fc_prs.words);

	if (fc_state->transformed)
	{
		pfree(fc_prs.startsel);
		pfree(fc_prs.stopsel);
	}

	PG_RETURN_TEXT_P(fc_out);
}

Datum ts_headline_json(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_json_byid_opt,
										ObjectIdGetDatum(getTSCurrentConfig(true)),
										PG_GETARG_DATUM(0),
										PG_GETARG_DATUM(1)));
}

Datum ts_headline_json_byid(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_json_byid_opt,
										PG_GETARG_DATUM(0),
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(2)));
}

Datum ts_headline_json_opt(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall4(ts_headline_json_byid_opt,
										ObjectIdGetDatum(getTSCurrentConfig(true)),
										PG_GETARG_DATUM(0),
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(2)));
}


/*
 * 返回来自 json(b) 元素生成的文本形式的标题
 */
static text * fc_headline_json_value(void *fc__state, char *fc_elem_value, int fc_elem_len)
{
	HeadlineJsonState *fc_state = (HeadlineJsonState *) fc__state;

	HeadlineParsedText *fc_prs = fc_state->prs;
	TSConfigCacheEntry *fc_cfg = fc_state->cfg;
	TSParserCacheEntry *fc_prsobj = fc_state->prsobj;
	TSQuery		fc_query = fc_state->query;
	List	   *fc_prsoptions = fc_state->prsoptions;

	fc_prs->curwords = 0;
	hlparsetext(fc_cfg->cfgId, fc_prs, fc_query, fc_elem_value, fc_elem_len);
	FunctionCall3(&(fc_prsobj->prsheadline),
				  PointerGetDatum(fc_prs),
				  PointerGetDatum(fc_prsoptions),
				  PointerGetDatum(fc_query));

	fc_state->transformed = true;
	return generateHeadline(fc_prs);
}
